/*
 * Copyright (C) 2013  Richard Schilling. All rights reserved.
 * contact: coderroadie@gmail.com
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package android.persist.config;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;

import android.persist.PersistException;
import android.persist.annotation.DataStoreField;

/**
 * Extracts information from a {@link Field} that is needed to persist its data.
 * 
 * @author Richard Schilling (coderroadie@gmail.com)
 * @since 1.0
 */
public class FieldConfig {

    /**
     * Name of the class data store that this field is persisted in.
     * 
     * @since 1.0
     */
    public final String datastoreName;

    /**
     * The field that is persisted. The name of the field used to store the
     * field's data is returned from {@link #getPersistedName()}.
     * 
     * @see {@link #getPersistedName()}
     * @since 1.0
     */
    public final Field field;

    /**
     * If not null, this member contains the DataStoreField annotation that
     * {@link #field} was tagged with. If null, then {@link #field} was not
     * tagged with that annotation.
     * 
     * @since 1.0
     */
    public final DataStoreField annotation;

    /**
     * Used by {@link ClassConfig}.
     * 
     * @since 1.0
     */
    /* default */FieldConfig(String datastoreName, Field field) {

        if (datastoreName == null) {
            throw new IllegalArgumentException("tableName cannot be null");
        }

        if (field == null) {
            throw new IllegalArgumentException("field cannot be null");
        }

        this.datastoreName = datastoreName;
        this.field = field;
        this.annotation = field.isAnnotationPresent(DataStoreField.class) ? field
                .getAnnotation(DataStoreField.class) : null;

        if (!field.isAccessible()) {
            try {
                this.field.setAccessible(true);
            } catch (SecurityException e) {
                throw new PersistException(e);
            }
        }

        if (this.annotation != null) {
            // make sure that if key is specified, it's a primitive

            if (!isDomestic() && this.annotation.key())
                throw new IllegalArgumentException("field " + this.field.getName()
                        + " is a foreign reference and cannot be a key");

        }

    }

    /**
     * Returns the name of the field used to store {@link #field}'s data in
     * {@link #datastoreName}.
     * 
     * @return the field's name.
     * @since 1.0
     */
    public String getPersistedName() {
        return field.getName();
    }

    /**
     * {@inheritDoc}
     * 
     * @since 1.0
     */
    @Override
    public int hashCode() {
        return field.hashCode();
    }

    /**
     * {@inheritDoc}
     * 
     * @since 1.0
     */
    @Override
    public String toString() {
        return getPersistedName();
    }

    /**
     * Returns true if the field's data can be stored directly by any underlying
     * data store. If this function returns false then the data store will treat
     * the this object as a foreign reference.
     * 
     * @return true if the {@link #field}'s type is a java primitive or one of
     *         the classes in {@link #DIRECTLY_STORABLE}.
     * @see {@link #DIRECTLY_STORABLE}
     * @since 1.0
     */
    public boolean isDomestic() {
        return ClassConfig.isDomesticClass(field.getType());

    }

    public boolean isCollection() {
        return Collection.class.isAssignableFrom(field.getType());
    }

    public Class<?> getCollectionType() {
        if (!isCollection()) {
            throw new UnsupportedOperationException(
                    "this function is only available if field is a collection.  this field, "
                            + field.getName()
                            + ", is not a collection.");
        }

        ParameterizedType genericType = (ParameterizedType) field.getGenericType();
        return (Class<?>) genericType.getActualTypeArguments()[0];

    }
}
